# pip3 install pycryptodomex
# pip3 install requests
import socket, sys
import base64, random, time, requests, json
from Cryptodome import Random
from Cryptodome.PublicKey import RSA
from Cryptodome.Hash import SHA1
from Cryptodome.Cipher import PKCS1_v1_5 as CPKCS8  # 加密解密模块
from Cryptodome.Signature import pkcs1_15  # 加签验签模块
import urllib.parse

from mysqldb.reconf import Receipt
from rest_framework import serializers

allinpay = Receipt()

Basics = {
    "pays": "pay/",
    "scan": "scanqrpay/",
    "cancel": "cancel/",
    "refund": "refund/",
    "query": "query/",
}


def has_Random(l=6):
    """返回随机数"""
    s = ""
    for i in range(l):
        s += str(random.choice(range(6)))
    return s


def has_reqsn(l=3):
    """返回新的收单订单号"""
    return f'TY{time.strftime("%Y%m%d%H%M%S", time.localtime())}{has_Random(l)}'


def getRandom(l=8):
    """创建订单随机码"""
    s = ""
    for i in range(l):
        s += str(random.choice(range(9)))
    return s


def checkRsaKey(k):
    """校验密钥"""
    try:
        k = base64.b64decode(k)
        return RSA.importKey(k)
    except BaseException as err:
        raise serializers.ValidationError({"RsaKey": "校验密钥失败."})


def checkUrl(url):
    """校验网址"""
    try:
        data = requests.get(url, timeout=3)
        code = data.status_code
        if code == 200:
            return True
    except BaseException as err:
        pass
    raise serializers.ValidationError({"Url": "校验网址不可到达."})


class Urlchuli:
    """Url处理类，需要传入两个实参：Urlchuli('实参','编码类型')，默认utf-8
    url编码方法：url_bm() url解码方法：url_jm()"""

    def __init__(self, can, mazhi="utf-8"):
        self.can = can
        self.mazhi = mazhi

    def url_bm(self):
        """url_bm() 将传入的中文实参转为Urlencode编码"""
        quma = str(self.can).encode(self.mazhi)
        bianma = urllib.parse.quote(quma)
        return bianma

    def url_jm(self):
        """url_jm() 将传入的url进行解码成中文"""
        quma = str(self.can)
        jiema = urllib.parse.unquote(quma, self.mazhi)
        return jiema


def dict_to_str(di):
    """排序"""
    s = ""
    di = [(k, di[k]) for k in sorted(di.keys())]
    for k, v in di:
        if not v:
            continue
        s += f"{k}={v}&"
    return s[:-1]


def RSA_create(bits=2048):
    """创建密钥对"""
    random_generator = Random.new().read
    rsa_key = RSA.generate(bits, random_generator)
    return rsa_key.exportKey().decode("utf-8"), rsa_key.publickey().exportKey().decode(
        "utf-8"
    )


def RSA_ensign(s):
    """RSA 私钥加签"""
    try:
        s = dict_to_str(s)
        bprivate = base64.b64decode(allinpay.rsaprivate)
        key = RSA.importKey(bprivate)
        hash_obj = SHA1.new(s.encode("utf-8"))
        signature = pkcs1_15.new(key).sign(hash_obj)
        sing = base64.b64encode(signature).decode("utf-8")
        sing = Urlchuli(sing, "gbk").url_bm().replace("/", f"%2f")
        return sing
    except BaseException as err:
        raise serializers.ValidationError({"allinpay": "加签失败."})


def RSA_design(s):
    """RSA 公钥验签"""
    try:
        signature = s.pop("sign")
        s = dict_to_str(s)
        key = RSA.importKey(base64.b64decode(allinpay.bnkpublic))
        hash_obj = SHA1.new(s.encode("utf-8"))
        pkcs1_15.new(key).verify(hash_obj, base64.b64decode(signature))
        return s
    except BaseException as err:
        raise serializers.ValidationError({"allinpay": "收单验签失败"})


def post_bank_Querys(url, message):
    """提交请求"""
    s = dict_to_str(message)
    try:
        res = requests.post(
            url, headers={"Content-Type": "application/x-www-form-urlencoded"}, data=s
        )
        r = res.json()
    except BaseException as Err:
        print(Err)
        raise serializers.ValidationError({"allinpay": "收单请求错误"})
    if RSA_design(r):
        return r


def form_dict_format_post(pay, act):
    """请求预处理"""
    if not allinpay.status or not allinpay.is_active:
        raise serializers.ValidationError({"allinpay": "收单接口已关闭."})
    # 加入消息通知字段
    if act in ["pays", "scan"]:
        pay["notify_url"] = allinpay.notify
    # 加入商户ID字段
    pay["cusid"] = allinpay.shopid
    # 加入商户appid字段
    pay["appid"] = allinpay.appid
    # 加入随机码字段
    pay["randomstr"] = getRandom()
    # 加入签名方法
    pay["signtype"] = "RSA"
    # 加入签名值
    pay["sign"] = RSA_ensign(pay)
    # 拼接请求接口地址
    url = allinpay.interface + Basics.get(act)
    # 提交请求
    sess = post_bank_Querys(url, pay)
    return sess
